#include <config.h>
#include <sys/time.h>
#include <unistd.h>

#include "CPU.h"
#include "arch.h"
#include "base/Configuration.h"
#include "base/Processor.h"
#include "base/Profiler.h"
#include "base/QuiesceRequest.h"
#include "base/Simulator.h"
#include "base/StatsElector.h"
#include "base/UnQuiesceRequest.h"
#include "base/Watcher.h"
#include "base/isa_ext.h"
#include "base/signals/SigHandler.h"
#include "base/signals/SigManager.h"
#include "base/simapi.h"
#include "base/syscalls.h"
#include "base/threads/PthreadInterface.h"
#include "mem/SplitBus.h"
#include "mem/SplitBus.h"
#include "mem/UpdateRequest.h"
#include "mem/VerifyRequest.h"
#include "base/threads/PthreadInterfaceLock.h"

NEW_DELETE_OPERATORS_CPP(Simulator,1);

Simulator* global_sim = NULL;
Logger* traceLog;
Logger* quiesceLog;

// this should be defined in the arch specific CPU.cpp
void NativeSafetyExitProc ();

NEW_DELETE_OPERATORS_TEMPLATE_CPP(__quote__(HashMap<VA,Time*>), 100);

extern "C" Simulator* makeSimulator_ideal () {
    return NEW Simulator("ideal");
}


Simulator::Simulator (const char* sysName_) :
    running(false),
    done(false),
    simclock(0),
    simclockStats(0),
    TM(NULL),
    sysName(sysName_),
    mode(MODE_UNKNOWN),
    top(NEW Entity("TOP", CLOCK_FIRST)), // this is not clocked
    syscallLock(NULL),
    gdb(NULL)
{
    global_sim = this;
    inSimulation = true;

    init_isa_ext();
    init_syscalls();
    arch_initSimulation();

    traceLog   = NEW Logger("traceLog", "", "trace.log:w", "trace log");
    quiesceLog = NEW Logger("quiesceLog", "qui", "-:w,log.sim", "quiesce logging");

    // get configuration values
    cfg.num_cpus = global_cfg->getIntegerArgument("num_cpus",
                                                  "number of CPUs in the system",
                                                  8,
                                                  CONFIG_RANGE,
                                                  1,
                                                  MAX_NUM_CPUS);
    cfg.blocksize = global_cfg->getIntegerArgument("cache_L1_blocksize",
                                                   "Block size of L1 caches, in bytes",
                                                   32, 
                                                   CONFIG_MIN,
                                                   8);
    cfg.stats_enabled = global_cfg->getBooleanArgument("stats_default",
                                                       "default value for stats_enabled",
                                                       true);
    cfg.shutdown_asserts = global_cfg->getBooleanArgument("shutdown_asserts",
                                                          "verify assertions during shutdown",
                                                          true);
    cfg.watchdog = global_cfg->getIntegerArgument("watchdog",
                                                  "ticks between running the watchdog",
                                                  30 * 1000,
                                                  CONFIG_UNSIGNED);

    cfg.proc_watchdog = global_cfg->getIntegerArgument("proc_watchdog",
                                                       "per processor watchdog (0 = off).",
                                                       0,
                                                       CONFIG_UNSIGNED);

    cfg.profile = global_cfg->getBooleanArgument("profile",
                                                 "output PC profiling information to profile file");
    cfg.watcherEnabled = global_cfg->getBooleanArgument("watcherEnabled",
                                                        "display address contents after a specifed simclock for debugging");
    cfg.watcherSimclock = global_cfg->getIntegerArgument("watcherSimclock",
                                                         "simclock for use with watcherEnabled. (0 = off, requiring programmatic enabling)",
                                                         0,
                                                         CONFIG_UNSIGNED);
    cfg.watcherAddress = global_cfg->getUnsignedArgument("watcherAddress",
                                                         "address for use with watcherEnabled. (0 = off, requiring programmatic enabling)",
                                                         0);
    cfg.ipc2 = global_cfg->getBooleanArgument("ipc2",
                                              "try ipc of 2",
                                              false);
    cfg.gdbWait = global_cfg->getBooleanArgument("gdb_wait",
                                                 "wait for gdb to attach before starting program simulation",
                                                 false);
    cfg.gdbPort = global_cfg->getIntegerArgument("gdb_port",
                                                 "port to attach gdb to debug application (0 = dynamically selected).",
                                                 0,
                                                 CONFIG_UNSIGNED);

    getWatchAddresses();

    TM  = NEW ThreadManager(cfg.num_cpus);
    SM  = NEW SigManager();
    gdb = NEW GDB();

    if (cfg.profile) {
        profileTables = (HashMap<VA,Time*>**)CALLOC(cfg.num_cpus, sizeof(HashMap<VA,Time*>*));
        for (uint i = 0; i < cfg.num_cpus; i++) {
            profileTables[i] = NEW HashMap<VA,Time*>(128*1024);
        }
        profiler = NEW Profiler();
        entities.push_back(profiler);
        activate(profiler);
    }

    if (cfg.watcherEnabled) {
        watcher = NEW Watcher(cfg.watcherSimclock, cfg.watcherAddress);
        entities.push_back(watcher);
        activate(watcher);
    }

    // Global Statistics
    // note that the : is required so that when printed we have a RFC822
    // format file
    global_statistics = NEW StatisticsCollection("Global: Stats");
}

Simulator::~Simulator () {
    if (!entities.empty()) {
        for (uint i=0; i<entities.size(); i++) {
            if (entities[i]) {
                delete entities[i];
            }
        }
    }

    if (cfg.profile) {
        profileTableOutput();
    }

    delete SM;
    delete TM;

    deleteProcessors();

    delete gdb;
    delete traceLog;
    delete quiesceLog;

    delete global_statistics;

    delete top;
}

uint Simulator::getMemLatency (int default_latency) {
    return global_cfg->getIntegerArgument("memory_latency",
                                          "main memory latency in cycles",
                                          default_latency,
                                          CONFIG_UNSIGNED);
}

bool Simulator::hasPrivateL2 () {
    return global_cfg->getBooleanArgument("cache_L2",
                                          "use a private L2 cache",
                                          false);
}

void Simulator::buildSystem (void* mainX, 
                             int argc, 
                             const char** argv, 
                             const char** envp) 
{

    // build an ideal system, derived class will almost always override
    mode = MODE_LOCK;
    buildProcessors(mainX, argc, argv, envp);

    int mem_latency = getMemLatency(0);
    if (cfg.num_cpus > 1 && mem_latency != 0) {
        Log->e("Multiprocessor with ideal memory requires 0 latency. (If you want non-zero latency, you can only have one processor)");
    }
    memory = NEW Memory("MM", mem_latency);

    // add the main memory
    entities.push_back(memory);
    activate(memory);

    for (uint i = 0; i < cfg.num_cpus; i++) {
        procs[i]->addRoute(memory, memory);
        memory->addRoute(procs[i], procs[i]);
    }
}

Entity* Simulator::getTop () {
    return top;
}

void Simulator::getWatchAddresses () {
    numWatches = 0;
    watchMask = (cfg.blocksize -1) ^ (uint)-1;
    const char* addrs = global_cfg->getStringArgument("watch_addr",
                                                      "comma seperated list of addresses to watch",
                                                      "");
    char addr[100];
    const char* t1;
    char* t2;
    for (t1=addrs, t2=addr; *t1; t1++) {
        switch (*t1) {
        case ',':
            *t2 = '\0';
            t2 = addr;
            uint waddr;
            if (sscanf(addr, "%i", &waddr) != 1) {
                Log->e("Bad watch address format:%s", addrs);
            }
            watchAddresses[numWatches] = waddr;
            watchAddresses[numWatches++] &= watchMask;
            break;
        default:
            *t2++ = *t1;
        }
    }
    if (t2 != addr) {
        *t2 = '\0';
        t2 = addr;
        uint waddr;
        if (sscanf(addr, "%i", &waddr) != 1) {
            Log->e("Bad watch address format:%s", addrs);
        }
        watchAddresses[numWatches] = waddr;
        watchAddresses[numWatches++] &= watchMask;
    }
}

bool Simulator::isWatched (VA addr) {
    if (!numWatches) {
        return true;
    }
    addr &= watchMask;
    for (uint i = 0; i < numWatches; i++) {
        if (watchAddresses[i] == addr) {
            return true;
        }
    }
    return false;
}

void Simulator::printStatistics () {

    // give processors chance to flush leftover stats to global stats
    for (uint i=0; i<cfg.num_cpus; i++) {
        procs[i]->hookPrintStatistics();
    }

    FILE* statFile;
    {
        char _tmp[2048];
        Logger::getOutputPath(_tmp, "statistics");
        statFile = fopen(_tmp, "w");
        ASSERT(statFile);
    }

    // The "statistics" file format is a sequenece of RFC822 header
    // fields.  As with RFC822 messages, a blank line separates the
    // headers from the following content. Tools that parse the
    // "statistics" file expect it to consist on a single "global"
    // section followed by on section per processor. Do not add new
    // sections without considering the significant downstream impact
    // this will cause.
    ASSERT(getSimclockStats() <= getSimclock());
    fprintf(statFile, "Total-Cycles: %lld\n", getSimclockStats());
    getStatistics()->printStatisticsCollection(statFile, "");

    hookPrintStatistics(statFile);
    fclose(statFile);
}

void Simulator::hookPrintStatistics (FILE* statFile) {
    // do nothing
}

void Simulator::statsCountVotes () {
    for (uint i = 0; i < cfg.num_cpus; i++) {
        procs[i]->countVotes();
    }

    // count votes for global stats (like shared cache buckets)
    Vector<Bucket*>* buckets = global_statistics->getBuckets();

    Vector<Bucket*>::reverse_iterator bktIdx;
    for (bktIdx = buckets->rbegin();
         bktIdx != buckets->rend();
         bktIdx++)
    {
        Bucket* bkt = *bktIdx;
        if (bkt->getVoteStatus()) {
            bkt->elect();
            break;
        }
    }

    hookStatsCountVotes();
}

void Simulator::hookStatsCountVotes () {
    // do nothing
}

void Simulator::profile () {
    // if we aren't collecting stats, do not bother to profile
    if (!cfg.stats_enabled) {
        return;
    }
    for (uint i=0; i<cfg.num_cpus; i++) {
        Processor* proc = procs[i];
        // if processor is halted, don't profile it
        if (proc->isHalted()) {
            continue;
        }
        // if processor is marked to be igored, don't profile it
        if (proc->getWatchdogIgnoreProgress()) {
            continue;
        }
        profileTableIncrement(proc);
    }
}

void Simulator::profileTableIncrement (Processor* p) {
    // PC profiling
    VA pc = p->getCPU()->getPC();
    HashMap<VA,Time*>* profileTable = profileTables[p->getID()];
    Time* cycles = profileTable->lookup(pc);
    if (cycles == NULL) {
        Time* one = (Time*)CALLOC(1, sizeof(Time));
        *one = 1;
        (*profileTable)[pc] = one;
    }
    else {
        (*cycles)++;
    }
}

static HashMap<VA,Time*>* profileTableCompareGlobal;

int Simulator::profileTableCompare (const void *a, const void *b) {
    VA pcA = *(VA*)a;
    VA pcB = *(VA*)b;
    VA cyclesA = *(profileTableCompareGlobal->lookup(pcA));
    VA cyclesB = *(profileTableCompareGlobal->lookup(pcB));
    return cyclesB-cyclesA;
}

void Simulator::profileTableOutput () {
    FILE* profileFile;
    {
        char _tmp[2048];
        Logger::getOutputPath(_tmp, "profile");
        profileFile = fopen(_tmp, "w");
        ASSERT(profileFile);
    }

    for (uint i = 0; i < cfg.num_cpus; i++) {
        fprintf(profileFile, "Processor: %d\n", i);
        HashMap<VA,Time*>* profileTable = profileTables[i];
        VA* keys = (VA*)CALLOC(profileTable->size(), sizeof(VA));
        uint length = profileTable->keys(keys);
        ASSERT(length == profileTable->size());
        profileTableCompareGlobal = profileTable;
        qsort(keys, length, sizeof(VA), profileTableCompare);
        profileTableCompareGlobal = NULL;
        for (uint i = 0; i < length; i++) {
            VA pc     = keys[i];
            VA cycles = *(profileTable->lookup(pc));

            const int bufferSize = 8192;
            char buffer[bufferSize];
            char* index = buffer;
            index += sim_sprintf(index, "%.16llX: %lld", pc, cycles);

            int characters_on_line = index-buffer;
            int padding = 80-characters_on_line;
            index = Processor::appendSymbolInformation(index, pc, padding);

            index += sim_sprintf(index, "\n");
            ASSERT(index-buffer<bufferSize);

            fputs(buffer, profileFile);
        }
        FREE(keys);
        delete profileTable;
        fputs("\n", profileFile);
    }
    FREE(profileTables);

    int result = fclose(profileFile);
    ASSERT(result == 0);
}

void Simulator::init_isa_ext () {
    for (int i=ISA_EXT_MINIMUM; i<ISA_EXT_MAXIMUM; i++) {
        isa_ext_functions[i] = NULL;
        if (isa_ext_name(i) == NULL) {
            Log->e("No isa_ext_name defined for isa_ext=%d\n", i);
        }
    }
    register_isa_ext(ISA_EXT_GETNUMCPUS, &Simulator::isa_ext_get_num_cpus);
    register_isa_ext(ISA_EXT_MODE, &Simulator::isa_ext_get_mode);
}

//#define CHECK_GDB_INTERVAL 5000
#define CHECK_GDB_INTERVAL 50000000

void Simulator::run () {
    gettimeofday(&start_time, NULL);

    global_sim->setEndDataSegment(sbrk(0));

    int CheckGDBInterval = CHECK_GDB_INTERVAL;
    if (cfg.gdbWait) {
        Log->w("Waiting for gdb to attach on port %d\n", cfg.gdbPort);
        getGDB()->poll(true);
    }

    Vector<Entity*> e;
    static Vector<Entity*>::iterator i; // static to last across signals

    running = true;

    bool signaled = false;
    // jump point for signal handling.
    if (sigsetjmp(sigjmpEnv,1) != 0) {
        // siglongjmp() was called.
        signaled = true;
    }

    while (!done) {

        /*
          See if GDB's interface has anything to say.
          We look every CHECK_GDB_INTERVAL instructions or so.                                                                                                                                                                
        */
        if (--CheckGDBInterval <= 0) {
            CheckGDBInterval = CHECK_GDB_INTERVAL;
            getGDB()->poll(false);
        }

        if (!signaled) {
            // clear before, not after, to prevent memory leak in signal
            // handling case
            e.clear();
            for (int c = CLOCK_FIRST; c < CLOCK_SIZE; c++) {
                e.insert(e.end(), active[c].begin(), active[c].end());
            }
            i = e.begin();
        } else {
            Processor* P = sigProc ? sigProc : CAST_OR_DIE(Processor*, *i);
            // either synchronous signal happened while Procesor was being
            // ticked or we invoked and provided the proc to signal
            ASSERT(P != NULL);
            P->clearRepeater();
            P->getSigHandler()->trampolineToSigHandler(sigContext);
            signaled = false;
            i++; // go to next entity, don't want to give this one two ticks
                 // in one round
        }

        for (; i != e.end(); ++i) {
            Entity* entity = *i;
            if (cfg.ipc2 && dynamic_cast<Processor*>(entity)) {
                Processor* P = (Processor*)(entity);
                uint before =
                    P->ctrL1Accesses->peek();
                Logger::setCurEntity(entity);
                entity->tick();
                Logger::setCurEntity(NULL);
                uint after =
                    P->ctrL1Accesses->peek();
                // for IPC 2, see if we're doing a syscall, then dont
                // tick twice
                if (isActive(entity) &&
                    getSyscallLock() == NULL &&
                    before == after)
                {
                    Logger::setCurEntity(entity);
                    entity->tick();
                    Logger::setCurEntity(NULL);
                }
            } else {
                Logger::setCurEntity(entity);
                entity->tick();
                Logger::setCurEntity(NULL);
            }
        }

        // try to schedule asynchronous signals to available processors
        SM->runScheduledSignal();
        SM->runScheduledThreadSignal();
        if (!(simclock % cfg.watchdog)) {
            bool stalled = true;
            for (uint p = 0; p < procs.size() && stalled; p++) {
                Processor* proc = procs[p];
                if (proc->getWatchdogIgnoreProgress()) {
                    continue;
                }
                if (proc->watchdogTimeSinceProgress() <= cfg.watchdog) {
                    stalled = false;
                }
            }
            if (stalled) {
                for (uint p = 0; p < procs.size(); p++) {
                    Processor* proc = procs[p];
                    if (proc->watchdogTimeSinceProgress() > cfg.watchdog) {
                        Log->w("%s processor %d at PC "RegFormat" for %d cycles\n",
                               proc->getStatus(),
                               proc->getID(),
                               proc->getCPU()->getPC(),
                               cfg.watchdog);
                        if (proc->getCPU()->getPC() != 0) {
                            proc->getCPU()->printInstruction(Log,
                                                             SEV_WARNING,
                                                             false,
                                                             true);
                        }
                    }
                }
                Log->e("%d: Watchdog!\n", (uint)simclock);
            }
        }

        if (cfg.proc_watchdog > 0 && !(simclock % cfg.proc_watchdog)) {
            for (uint p = 0; p < procs.size(); p++) {
                Processor* proc = procs[p];
                if (proc->getWatchdogIgnoreProgress()) {
                    continue;
                }
                if (!proc->isHalted() &&
                    proc->watchdogTimeSinceProgress() > cfg.proc_watchdog) 
                {
                        Log->w("Stalled processor %d at PC "RegFormat" for %lli cycles\n",
                               proc->getID(),
                               proc->getCPU()->getPC(),
                               proc->watchdogTimeSinceProgress());
                        proc->getCPU()->printInstruction(Log,
                                                         SEV_WARNING,
                                                         false,
                                                         true);
                    Log->e("%d: Processor watchdog!\n", (uint)simclock);
                }
            }
        }
        
        simclock++;
        if (cfg.stats_enabled) {
            simclockStats++;
        }
    }
}

void Simulator::stop (int code = 0) {
    running = false;
    done = true;
    exitCode = code;

    // If any processor is still running, asserts will probably fail.
    // This is very common in Java which doesn't cleanup pthreads
    // before exiting.
    for (uint i = 0; i < cfg.num_cpus; i++) {
        if (!procs[i]->isHalted()) {
            cfg.shutdown_asserts = false;
            Pool::set_shutdown_asserts(false);
            break;
        }
    }
}

bool Simulator::isHalted(int procID) {
    return procs[procID]->isHalted();
}

Processor* Simulator::getIdleProc () {
    for (uint i=0; i<cfg.num_cpus; i++) {
        if (procs[i]->isHalted()) {
            return procs[i];
        }
    }
    return NULL;
}

Processor* Simulator::getProcForSignal () {
    for (uint i=0; i<cfg.num_cpus; i++) {
        if (procs[i]->canHandleSignal()) {
            return procs[i];
        }
    }
    return NULL;
}

Processor* Simulator::getProcForThreadSignal (int procID) {
  if (procs[procID]->canHandleSignal()) {
        return procs[procID];
  }
  return NULL;
}

void Simulator::noteSignal (SigContext* sigContext, Processor* sigProc) {
    this->sigContext = sigContext;
    this->sigProc = sigProc;
}

uint Simulator::getNumRunningCpus () {
    int runningProcs = 0;
    for (uint i = 0; i < procs.size(); i++) {
        // check if the processor is running.
        if (procs[i]->isRunning()) {
            runningProcs++;
        }
    }
    return runningProcs;
}

PthreadInterface* Simulator::buildPthreadInterface () {
    return NEW PthreadInterfaceLock();
}

void Simulator::registerSyscallHooks () {
    register_pre_syscall_hook(std_pre_syscall_hook);
    register_post_syscall_hook(std_post_syscall_hook);
}

void Simulator::buildProcessors (void* mainX,
                                 int argc,
                                 const char** argv,
                                 const char** envp)
{
    // now make some processors and connect them to it
    processors = (Processor**)CALLOC(cfg.num_cpus, sizeof(Processor*));
    for (uint i = 0; i < cfg.num_cpus; i++) {
        char name[5];
        sim_sprintf(name, "%d", i);

        Processor* processor = buildProcessor(name, i);
        procs.push_back(processor);
        ASSERT(procs.size()-1 == i);
        ASSERT(procs[i] == processor);
        entities.push_back(processor);
        int extraEntities = (cfg.profile        ? 1 : 0 +
                             cfg.watcherEnabled ? 1 : 0);
        ASSERT(entities.size() - (1 + extraEntities) == i);
        ASSERT(entities[i + extraEntities] == processor);
        processors[i] = processor;
    }
    // reset the first processor to mainX
    procs[0]->reset(ADDR_UINT64(mainX),
                    ADDR_UINT64((void*)&NativeSafetyExitProc),
                    3,
                    argc,
                    (Reg)ADDR_UINT64(argv),
                    (Reg)ADDR_UINT64(envp),
                    SIMARG_UNUSED);

    // we register a filter for syscall to check if a syscall is called
    registerSyscallHooks();

    // Tell the thread manager what pthread interface to use
    TM->setPthreadInterfaceSim(buildPthreadInterface());

    StatsElector* se = NEW StatsElector();
    entities.push_back(se);
    activate(se);
}

void Simulator::deleteProcessors ()
{
    FREE(processors);
}

Processor* Simulator::buildProcessor (const char* name, int id) {
    return NEW Processor(this, name, id);    
}


void Simulator::register_isa_ext (int isa_ext,
                                  bool (*function)(Processor* P,
                                                   Reg arg0,
                                                   Reg arg1,
                                                   Reg* result),
                                  bool clobberOkay)
{
    if (!clobberOkay && isa_ext_functions[isa_ext] != NULL) {
        Log->e("Two ISA extension functions registered for the same extension number: %i\n",
               isa_ext);
    }
    isa_ext_functions[isa_ext] = function;
}

bool Simulator::handle_isa_ext (int isa_ext,
                                Processor* P,
                                Reg arg0,
                                Reg arg1,
                                Reg* result)
{
    if (isa_ext_functions[isa_ext] == NULL) {
        Log->w("%d:%lld:%.16llX Unregistered ISA extension %s(%d) called.\n",
               P->getID(),
               global_sim->getSimclock(),
               P->getCPU()->getPC(),
               isa_ext_name(isa_ext),
               isa_ext);
        // only warn once, then register noop
        register_isa_ext(isa_ext, &Simulator::isa_ext_noop);
        return true;
    }
    if (P->firstCycle) {
        Log->d("%d:%lld:%.16llX ISA extension %d called %s(%x(%d),%x(%d))\n",
               P->getID(),
               global_sim->getSimclock(),
               P->getCPU()->getPC(),
               isa_ext,
               isa_ext_name(isa_ext),
               (int)arg0, (int)arg0,
               (int)arg1, (int)arg1);
    }
    return (*isa_ext_functions[isa_ext])(P, arg0, arg1, result);
}

bool Simulator::isa_ext_noop (Processor* P, Reg arg0, Reg arg1, Reg* result) {
    return true;
}

bool Simulator::isa_ext_get_num_cpus (Processor* P,
                                      Reg arg0,
                                      Reg arg1,
                                      Reg* result)
{
    *result = global_sim->getNumCpus();
    return true;
}

bool Simulator::isa_ext_get_mode (Processor* P,
                                  Reg arg0,
                                  Reg arg1,
                                  Reg* result)
{
    *result = global_sim->getMode();
    return true;
}

bool Simulator::isActive (Entity* P) {
    for (int c = CLOCK_FIRST; c < CLOCK_SIZE; c++) {
        List<Entity*>::iterator i;
        for (i = active[c].begin(); i != active[c].end(); i++) {
            if ((*i) == P) {
                return true;
            }
        }
    }
    return false;
}

void Simulator::activate (Entity* entity) {
    entity->activate();
    active[entity->getClockOrder()].add(entity);
}

void Simulator::deactivate (Entity* entity) {
    entity->deactivate();
    active[entity->getClockOrder()].remove(entity);
}

void Simulator::invalidateCaches (Processor* processor, const char* description, VA lb, size_t n) {
    Log->d("%d:%lld: invalidateCaches %s %llx+%d %llx-%llx\n",
           processor->getID(), global_sim->getSimclock(), description, lb, n, lb, lb+n);
    for (uint i = 0; i < cfg.num_cpus; i++) {
        InvalidateRequest* ir = NEW InvalidateRequest(NULL,
                                                      getMemory(),
                                                      lb,
                                                      lb+n);
        // invalidates all caches...
        procs[i]->handle(ir);
        delete ir;
    }
}

void Simulator::updateCaches () {
    for (uint i = 0; i < cfg.num_cpus; i++) {
        UpdateRequest* ur = NEW UpdateRequest(NULL, getMemory());
        procs[i]->handle(ur);
        delete ur;
    }
}

void Simulator::rawFlushCaches (VA start, size_t len) {
    // if we want to flush all of memory, we have to be quiesced
    ASSERT(start != 0 || isQuiesced());
    for (uint i = 0; i < cfg.num_cpus; i++) {
        procs[i]->rawFlushCaches(start, len);
    }
}

List<Processor*>* Simulator::verifyCaches () {
    // send a message to each processor telling it to check its caches
    // against memory... return a list of those whose caches fail the
    // validation...

    List<Processor*>* l = NEW List<Processor*>();

    for (uint i = 0; i < cfg.num_cpus; i++) {
        VerifyRequest* r = NEW VerifyRequest(procs[i],
                                             getMemory());
        Result p = procs[i]->handle(r);
        ASSERT(p == success);

        // that verification was "instant" (took no sim cylces)
        if (r->failed) {
            l->add(procs[i]);
        }

        delete r;
    }

    return l;
}

void Simulator::quiesce (Entity* e) {
    ASSERT(e != NULL);
    ASSERT(quiescedList == NULL);
    quiescedList = NEW List<Entity*>();

    quiesceLog->d("%s:%lli: quiesce requested by %s\n", 
                  e->getName(), getSimclock(), e->getName()); 

    hookResetQuiesce();
    
    // we pre-count and set the number of cores to be quiesced since
    // in some cases quiesce requests reach to the bus at the same cycle from here.
    numToBeQuiesced = 0;
    for (uint i = 0; i < cfg.num_cpus; i++) {
        if (procs[i] == e) {
            continue;
        }

        numToBeQuiesced++;
    }
    hookCountQuiesceEntities();

    for (uint i = 0; i < cfg.num_cpus; i++) {
        if (procs[i] == e) {
            continue;
        }

        quiesceEntity(procs[i]);
    }
    hookQuiesceEntities();
}

void Simulator::hookResetQuiesce () {
    // do nothing here
}

void Simulator::hookCountQuiesceEntities () {
    // do nothing here
}

void Simulator::hookQuiesceEntities () {
    // do nothing here
}

void Simulator::hookUnQuiesceEntities () {
    // do nothing here
}

uint Simulator::getNumQuiescingEntities () {
    return numToBeQuiesced;
}

void Simulator::quiesceEntity (Entity* e) {
    QuiesceRequest* qr = NEW QuiesceRequest(NULL, e);
    quiescedList->add(e);
    Result r = e->handle(qr);
    ASSERT(r == success);
}


bool Simulator::isQuiesceActive () {
    return quiescedList != NULL;
}

bool Simulator::isQuiesced () {
    ASSERT(quiescedList != NULL);
    List<Entity*>::iterator i;
    for (i = quiescedList->begin(); i != quiescedList->end(); i++) {
        Entity* entity = *i;
        Processor* processor = dynamic_cast<Processor*>(entity);
        quiesceLog->d("%s:%lli %s has not quiesced %s active=%d\n",
                      entity->getName(),
                      getSimclock(),
                      entity->getName(),
                      processor == NULL ? "" : processor->getStatus(),
                      isActive(processor));
    }
    return quiescedList->empty();
}

// this is the callback from the processor to let us know that its
// quiesced
//
void Simulator::quiesced (Entity *e) {
    quiesceLog->d("%s:%lli %s has quiesced\n",
                  e->getName(),
                  getSimclock(),
                  e->getName());
    quiescedList->remove(e);
}

void Simulator::unquiesce (Entity* e) {
    ASSERT(e != NULL);
    delete quiescedList;
    quiescedList = NULL;

    quiesceLog->d("%s:%lli: unquiesce requested by %s\n", 
                  e->getName(), getSimclock(), e->getName());

    for (uint i = 0; i < cfg.num_cpus; i++) {
        if (procs[i] == e) {
            continue;
        }
        UnQuiesceRequest* qr = NEW UnQuiesceRequest(NULL, procs[i]);
        Result r = procs[i]->handle(qr);
        ASSERT(r == success);
    }
    hookUnQuiesceEntities();
}

bool Simulator::isDone () {
    return done;
}

bool Simulator::acquireSyscallLock (Entity* p) {
    // we don't support a reentrant syscall lock
    ASSERT(!hasSyscallLock(p));
    if (syscallLock == NULL) {
        syscallLock = p;
        
        quiesceLog->d("%s:%lli: syscall lock is acquired by %s\n", 
                      p->getName(),
                      getSimclock(),
                      p->getName());

        return true;
    }
    return false;
}

bool Simulator::hasSyscallLock (Entity* p) {
    return syscallLock == p;
}

void Simulator::releaseSyscallLock (Entity* p) {
    ASSERT(hasSyscallLock(p));
    quiesceLog->d("%s:%lli: syscall lock is released by %s\n",
                  syscallLock->getName(),
                  getSimclock(),
                  syscallLock->getName());

    syscallLock = NULL;
}

Entity* Simulator::getSyscallLock () {
    return syscallLock;
}

bool Simulator::getRegister (int cpunum, int regnum, size_t len, uint8* val) {
    return procs[cpunum]->getCPU()->getRegister(regnum, len, val);
}

bool Simulator::putRegister (int cpunum, int regnum, size_t len, uint8* val) {
    return procs[cpunum]->getCPU()->putRegister(regnum, len, val);
}

bool Simulator::getMemory (int cpunum, VA vAddr, uint nbytes, uint8 *buf) {
    return procs[cpunum]->getMemory(vAddr, nbytes, buf);
}

bool Simulator::putMemory (int cpunum, VA vAddr, uint nbytes, uint8 *buf) {
    return false;
}
